"""
Base file parser class and common utilities
"""
import hashlib
import mimetypes
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Dict, Any, Optional, List, Union
import logging

logger = logging.getLogger(__name__)


class FileType(Enum):
    """Supported file types for parsing"""
    PDF = "pdf"
    WORD = "word"
    EXCEL = "excel" 
    MARKDOWN = "markdown"
    IMAGE = "image"
    PLAIN_TEXT = "text"
    UNKNOWN = "unknown"


@dataclass
class ParseResult:
    """Result of file parsing operation"""
    success: bool
    content: str = ""
    metadata: Dict[str, Any] = field(default_factory=dict)
    error: Optional[str] = None
    file_type: Optional[FileType] = None
    file_size: int = 0
    processing_time: float = 0.0
    page_count: Optional[int] = None
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary for JSON serialization"""
        result = {
            "success": self.success,
            "content": self.content,
            "metadata": self.metadata,
            "error": self.error,
            "file_type": self.file_type.value if self.file_type else None,
            "file_size": self.file_size,
            "processing_time": self.processing_time
        }
        if self.page_count is not None:
            result["page_count"] = self.page_count
        return result
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'ParseResult':
        """Create ParseResult from dictionary (for cache deserialization)"""
        file_type = None
        if data.get("file_type"):
            try:
                file_type = FileType(data["file_type"])
            except ValueError:
                file_type = FileType.UNKNOWN
        
        return cls(
            success=data.get("success", False),
            content=data.get("content", ""),
            metadata=data.get("metadata", {}),
            error=data.get("error"),
            file_type=file_type,
            file_size=data.get("file_size", 0),
            processing_time=data.get("processing_time", 0.0),
            page_count=data.get("page_count")
        )


class FileParser(ABC):
    """
    Abstract base class for file parsers
    """
    
    def __init__(self, max_file_size: int = 100 * 1024 * 1024):  # 100MB default
        self.max_file_size = max_file_size
        self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
    
    @abstractmethod
    def parse(self, file_path: Union[str, Path]) -> ParseResult:
        """
        Parse file and extract text content
        
        Args:
            file_path: Path to the file to parse
            
        Returns:
            ParseResult object with extracted content and metadata
        """
        pass
    
    @abstractmethod
    def supports_file_type(self, file_type: FileType) -> bool:
        """
        Check if parser supports the given file type
        
        Args:
            file_type: FileType to check
            
        Returns:
            True if supported, False otherwise
        """
        pass
    
    def validate_file(self, file_path: Union[str, Path]) -> bool:
        """
        Validate file before parsing
        
        Args:
            file_path: Path to the file to validate
            
        Returns:
            True if valid, False otherwise
        """
        file_path = Path(file_path)
        
        # Check if file exists
        if not file_path.exists():
            self.logger.error(f"File does not exist: {file_path}")
            return False
        
        # Check if it's a file (not directory)
        if not file_path.is_file():
            self.logger.error(f"Path is not a file: {file_path}")
            return False
        
        # Check file size
        file_size = file_path.stat().st_size
        if file_size > self.max_file_size:
            self.logger.error(f"File too large: {file_size} bytes (max: {self.max_file_size})")
            return False
        
        # Check if file is empty
        if file_size == 0:
            self.logger.warning(f"File is empty: {file_path}")
            return False
        
        return True
    
    def get_file_hash(self, file_path: Union[str, Path]) -> str:
        """
        Calculate MD5 hash of file for caching
        
        Args:
            file_path: Path to the file
            
        Returns:
            MD5 hash string
        """
        file_path = Path(file_path)
        hash_md5 = hashlib.md5()
        
        try:
            with open(file_path, "rb") as f:
                for chunk in iter(lambda: f.read(4096), b""):
                    hash_md5.update(chunk)
            return hash_md5.hexdigest()
        except Exception as e:
            self.logger.error(f"Error calculating file hash: {e}")
            return ""
    
    def get_file_metadata(self, file_path: Union[str, Path]) -> Dict[str, Any]:
        """
        Extract basic file metadata
        
        Args:
            file_path: Path to the file
            
        Returns:
            Dictionary with file metadata
        """
        file_path = Path(file_path)
        
        try:
            stat = file_path.stat()
            mime_type, _ = mimetypes.guess_type(str(file_path))
            
            metadata = {
                "file_name": file_path.name,
                "file_extension": file_path.suffix.lower(),
                "file_size": stat.st_size,
                "created_time": stat.st_ctime,
                "modified_time": stat.st_mtime,
                "mime_type": mime_type,
                "file_hash": self.get_file_hash(file_path)
            }
            
            return metadata
        except Exception as e:
            self.logger.error(f"Error extracting file metadata: {e}")
            return {}
    
    def create_error_result(self, error_message: str, file_path: Union[str, Path] = None) -> ParseResult:
        """
        Create a ParseResult for error cases
        
        Args:
            error_message: Error description
            file_path: Optional file path for metadata
            
        Returns:
            ParseResult with error information
        """
        metadata = {}
        file_size = 0
        
        if file_path:
            try:
                metadata = self.get_file_metadata(file_path)
                file_size = metadata.get("file_size", 0)
            except Exception:
                pass
        
        return ParseResult(
            success=False,
            error=error_message,
            metadata=metadata,
            file_size=file_size
        )
    
    def create_success_result(
        self, 
        content: str, 
        file_path: Union[str, Path], 
        file_type: FileType,
        processing_time: float = 0.0,
        additional_metadata: Dict[str, Any] = None
    ) -> ParseResult:
        """
        Create a successful ParseResult
        
        Args:
            content: Extracted text content
            file_path: Path to the parsed file
            file_type: Type of the parsed file
            processing_time: Time taken to parse
            additional_metadata: Additional metadata specific to parser
            
        Returns:
            ParseResult with extracted content
        """
        metadata = self.get_file_metadata(file_path)
        
        if additional_metadata:
            metadata.update(additional_metadata)
        
        return ParseResult(
            success=True,
            content=content,
            metadata=metadata,
            file_type=file_type,
            file_size=metadata.get("file_size", 0),
            processing_time=processing_time
        )